#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <map>

#include "shptr.h"
#include "sem.h"
#include "tcpsocket.h"

#define CONFIG_FILENAME "anillo.conf"

#define S_TRUE "true"
#define S_FALSE "false"

#define WHITESPACES " \t\f\v\n\r"

#define forever for(;;)

/**
 * Estructura del token. Preliminar! Por ahora tiene solo un entero que
 * cada host incrementa antes de devolver.
 */
typedef struct {
    int n;
} token_t;

/**
 * Tipo para el mapa que guarda la configuración.
 */
typedef std::map<std::string, std::string> config_t;

/**
 * Mapa con la configuración.
 */
config_t config;

/**
 * Semáforo para sincronizar los procesos cliente y servidor.
 */
Semaphore sem;

/**
 * Puntero compartido entre cliente y servidor para almacenar el token.
 */
SharedPointer<token_t, 1> token;

/**
 * Socket utilizado para enviar y recibir el token.
 */
TCPSocket sock;

/**
 * Convierte un string a entero.
 */
int to_int(const std::string& s) {
    return atoi(s.c_str());
}

/**
 * Elimina los espacios al comienzo y al final de un string.
 */
void chop(std::string& s) {
    s.erase(0, s.find_first_not_of(WHITESPACES));
    s.erase(s.find_last_not_of(WHITESPACES)+1);
}

/**
 * Carga la configuración de un archivo de texto.
 */
bool load_config() {
    std::ifstream file(CONFIG_FILENAME);
    std::string line, key, value;
    int pos;

    if(!file.good()) {
        return false;
    }

    getline(file, line);
    while(file.good()) {
        chop(line);

        if(!line.empty() && line.at(0)!='#') {
            pos = line.find("=");
            key = line.substr(0, pos);
            value = line.substr(pos+1);

            chop(key);
            chop(value);

            config[key] = value;
        }

        getline(file, line);
    }

//  DEBUG! imprime por pantalla la configuración cargada del archivo
//    config_t::iterator it = config.begin();
//    for(;it!=config.end(); it++) {
//        log << (*it).first << "=" << (*it).second << log.endl;
//    }

    return true;
}

/**
 * Callback de la señal SIGINT para los procesos cliente y servidor.
 * Desconecta el socket antes de terminar.
 */
void sigint_handler(int signal) {
    if(sock) {
        sock.close();
    }
    exit(0);
}

/**
 * Registra f como la función de callback de la señal s.
 */
void registrar_signal(int s, void (*f)(int)) {
    struct sigaction act;

    act.sa_handler = f;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);

    sigaction(s, &act, NULL);
}

/**
 * Proceso servidor. Recibe una conexión del host precedente en el anillo.
 * Una vez conectado, recibe el token y levanta el semáforo para activar el
 * proceso cliente.
 */
void run_server() {
    // socket utilizado para recibir la conexión entrante del
    // host precedente en el anillo
    TCPSocket sockserver;

    // configura el socket como pasivo con una cola de espera de 1 cliente
    if(!sockserver.listen(to_int(config["port"]), 1)) {
        log << "error en listen()" << log.err << log.endl;
        exit(1);
    }

    // acepta una conexión entrante
    if(!(sock = sockserver.accept())) {
        log << "error en accept()" << log.err << log.endl;
        exit(1);
    }

    // cierra el socket usado para establecer la conexión
    sockserver.close();

    forever {
        // recibe el token
        if(!sock.recv((token_t*)token)) {
            log << "error recibiendo token" << log.err << log.endl;
            break;
        } else {
            // levanta el semáforo para permitir que el proceso cliente
            // procese el token
            log << "recibí un token con valor " << token->n << log.endl;
            sem.signal();
        }
    }

    sock.close();

    exit(0);
}

/**
 * Proceso cliente. Intenta conectarse con el siguiente host en el anillo.
 * Luego de establecida la conexión, espera en el semáforo el arribo del token.
 * Finalmente, procesa el token y lo envía al siguiente host en el anillo.
 */
void run_client() {
    TCPSocket sock;

    // se conecta con el siguiente host del anillo
    // si el intento de conexión falla, reintenta cada 10 segundos
    // indefinidamente
    while(!sock.connect(config["next"], to_int(config["port"]))) {
        log << "error en connect()" << log.err << log.endl;
        log << "reintentando en 10 segundos" << log.endl;
        sleep(10);
    }

    // si se compiló con -DRING_START entonces este host debe
    // poner el token en el anillo
    if(config["first"]==S_TRUE) {
        token->n=0;
        sock.send((token_t*)token);
    }

    forever {
        // espera el arribo del token
        sem.wait();

        // a partir de acá debería hacerse el procesamiento del token
        // en este caso, espera un segundo e incrementa el contador
        sleep(1);
        token->n++;

        // reenvía el token
        if(!sock.send((token_t*)token)) {
            log << "error enviando token" << log.err << log.endl;
            break;
        } else {
            log << "envié un token con valor " << token->n << log.endl;
        }
    }

    sock.close();

    exit(0);
}

/**
 * Proceso principal. Inicializa el semáforo y el área compartida de memoria.
 * Lanza los procesos cliente y servidor. Si uno de ellos muere, mata al otro y
 * luego termina.
 */
int main() {
    pid_t pidsvr, pidclt, pidkilled;

    registrar_signal(SIGINT, SIG_IGN);
    log.set_pname("principal");

    if(!load_config()) {
        log << "imposible cargar configuración" << log.err << log.endl;
        exit(1);
    }

    // inicializa el semáforo
    if(!sem.init(0)) {
        log << "error inicializando semáforo" << log.err << log.endl;
        exit(1);
    }

    // inicializa el área compartida de memoria
    if(!token.allocate()) {
        log << "error alocando token" << log.err << log.endl;
        exit(1);
    }

    // lanza el proceso servidor
    if((pidsvr=fork())==0) {
        log.set_pname("servidor");
        registrar_signal(SIGINT, sigint_handler);
        run_server();
    } else if(pidsvr==-1) {
        log << "error en fork() del servidor" << log.err << log.endl;
        sem.destroy();
        token.free();
        exit(1);
    }

    // lanza el proceso cliente
    if((pidclt=fork())==0) {
        log.set_pname("cliente");
        registrar_signal(SIGINT, sigint_handler);
        run_client();
    } else if(pidclt==-1) {
        log << "error en fork() del cliente" << log.err << log.endl;
        kill(pidsvr, SIGINT);
        sem.destroy();
        token.free();
        exit(1);
    }

    // espera la terminación de uno de los hijos
    pidkilled = wait(NULL);
    if(pidkilled==pidsvr) {
        // si terminó el servidor, termina al cliente
        log << "terminó el proceso servidor, interrumpiendo cliente" << log.endl;
        kill(pidclt, SIGINT);
    } else if(pidkilled==pidclt) {
        // si terminó el cliente, termina al servidor
        log << "terminó el proceso cliente, interrumpiendo servidor" << log.endl;
        kill(pidsvr, SIGINT);
    }

    // espera la terminación del otro hijo. según el manual de wait(),
    // este llamado evita que queden procesos zombies
    wait(NULL);

    sem.destroy();
    token.free();

    return 0;
}
